#ifndef XMPPCONNECTION_H
#define XMPPCONNECTION_H

#include <string>
#include <sstream>
#include <vector>

#include <util/socket/SocketHandler.h>

class ConnectionConfiguration;
class PacketReader;
class PacketWriter;
class Roster;
class AccountManager;
class ChatManager;
class Presence;
class Packet;
class PacketListener;
class PacketFilter;
class PacketInterceptor;
class PacketCollector;
class ConnectionListener;
class SASLAuthentication;
class ConnectionCreationListener;
class UserAuthentication;
class Socket;

class XMPPConnection {
public:
	
    /**
     * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be
     * performed to determine the IP address and port corresponding to the
     * service name; if that lookup fails, it's assumed that server resides at
     * <tt>serviceName</tt> with the default port of 5222. Encrypted connections (TLS)
     * will be used if available, stream compression is disabled, and standard SASL
     * mechanisms will be used for authentication.<p>
     * <p/>
     * This is the simplest constructor for connecting to an XMPP server. Alternatively,
     * you can get fine-grained control over connection settings using the
     * {@link #XMPPConnection(ConnectionConfiguration)} constructor.<p>
     * <p/>
     * Note that XMPPConnection constructors do not establish a connection to the server
     * and you must call {@link #connect()}.
     *
     * @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>.
     */
	XMPPConnection(std::string & serviceName);


	/**
     * Creates a new XMPP connection using the specified connection configuration.<p>
     * <p/>
     * Manually specifying connection configuration information is suitable for
     * advanced users of the API. In many cases, using the
     * {@link #XMPPConnection(std::string)} constructor is a better approach.<p>
     * <p/>
     * Note that XMPPConnection constructors do not establish a connection to the server
     * and you must call {@link #connect()}.
     *
     * @param config the connection configuration.
     */
	XMPPConnection(ConnectionConfiguration * config);

	~XMPPConnection();

    /**
     * Returns the connection ID for this connection, which is the value set by the server
     * when opening a XMPP stream. If the server does not set a connection ID, this value
     * will be null. This value will be <tt>null</tt> if not connected to the server.
     *
     * @return the ID of this connection returned from the XMPP server or <tt>null</tt> if
     *         not connected to the server.
     */
	std::string getConnectionID() {
		return connectionID;
	};

    /**
     * Returns the name of the service provided by the XMPP server for this connection. After
     * authenticating with the server the returned value may be different.
     *
     * @return the name of the service provided by the XMPP server.
     */
	std::string getServiceName() {
		return serviceName;
	}

	void setServiceName(std::string s) { 
		serviceName = s;
	}

    /**
     * Returns the host name of the server where the XMPP server is running. This would be the
     * IP address of the server or a name that may be resolved by a DNS server.
     *
     * @return the host name of the server where the XMPP server is running.
     */
	std::string getHost() {
		return host;
	}
	
    /**
     * Returns the port number of the XMPP server for this connection. The default port
     * for normal connections is 5222. The default port for SSL connections is 5223.
     *
     * @return the port number of the XMPP server.
     */
    int getPort() {
		return port;
	}
	
    /**
     * Returns the full XMPP address of the user that is logged in to the connection or
     * <tt>null</tt> if not logged in yet. An XMPP address is in the form
     * username@server/resource.
     *
     * @return the full XMPP address of the user logged in.
     */
	std::string getUser() {
		return user;
	}

	PacketReader * getPacketReader() {
		return packetReader;
	}
	
	PacketWriter * getPacketWriter() {
		return packetWriter;
	}

    /**
     * Logs in to the server using the strongest authentication mode supported by
     * the server, then sets presence to available. If more than five seconds
     * (default timeout) elapses in each step of the authentication process without
     * a response from the server, or if an error occurs, a XMPPException will be thrown.
     *
     * @param username the username.
     * @param password the password.
     * @throws XMPPException if an error occurs.
     */
    void login(std::string & username, std::string & password) ;
	
    /**
     * Logs in to the server using the strongest authentication mode supported by
     * the server, then sets presence to available. If more than five seconds
     * (default timeout) elapses in each step of the authentication process without
     * a response from the server, or if an error occurs, a XMPPException will be thrown.
     *
     * @param username the username.
     * @param password the password.
     * @param resource the resource.
     * @throws XMPPException         if an error occurs.
     * @throws IllegalStateException if not connected to the server, or already logged in
     *                               to the serrver.
     */
    void login(std::string & username, std::string & password, std::string & resource) ;
	
    /**
     * Logs in to the server using the strongest authentication mode supported by
     * the server. If the server supports SASL authentication then the user will be
     * authenticated using SASL if not Non-SASL authentication will be tried. An available
     * presence may optionally be sent. If <tt>sendPresence</tt>
     * is false, a presence packet must be sent manually later. If more than five seconds
     * (default timeout) elapses in each step of the authentication process without a
     * response from the server, or if an error occurs, a XMPPException will be thrown.<p>
     * <p/>
     * Before logging in (i.e. authenticate) to the server the connection must be connected.
     * For compatibility and easiness of use the connection will automatically connect to the
     * server if not already connected.
     *
     * @param username     the username.
     * @param password     the password.
     * @param resource     the resource.
     * @param sendPresence if <tt>true</tt> an available presence will be sent automatically
     *                     after login is completed.
     * @throws XMPPException         if an error occurs.
     * @throws IllegalStateException if not connected to the server, or already logged in
     *                               to the serrver.
     */
    void login(std::string username, std::string password, std::string resource, bool sendPresence) ;
	
    /**
     * Logs in to the server anonymously. Very few servers are configured to support anonymous
     * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login
     * does succeed, your XMPP address will likely be in the form "server/123ABC" (where "123ABC"
     * is a random value generated by the server).
     *
     * @throws XMPPException         if an error occurs or anonymous logins are not supported by the server.
     * @throws IllegalStateException if not connected to the server, or already logged in
     *                               to the serrver.
     */
    void loginAnonymously() ;
	
	std::istream * getReader() { return reader;}
	std::ostream * getWriter() { return writer;}

    /**
     * Returns the roster for the user logged into the server. If the user has not yet
     * logged into the server (or if the user is logged in anonymously), this method will return
     * <tt>null</tt>.
     *
     * @return the user's roster, or <tt>null</tt> if the user has not logged in yet.
     */
    Roster * getRoster() ;
	
    /**
     * Returns an account manager instance for this connection.
     *
     * @return an account manager for this connection.
     */
    AccountManager * getAccountManager() ;

    /**
     * Returns a chat manager instance for this connection. The ChatManager manages all incoming and
     * outgoing chats on the current connection.
     *
     * @return a chat manager instance for this connection.
     */
    ChatManager * getChatManager() ;	
	
    /**
     * Returns true if currently connected to the XMPP server.
     *
     * @return true if connected.
     */
    bool isConnected() ;
	
    /**
     * Returns true if the connection is a secured one, such as an SSL connection or
     * if TLS was negotiated successfully.
     *
     * @return true if a secure connection to the server.
     */
    bool isSecureConnection() ;

	/**
     * Returns true if currently authenticated by successfully calling the login method.
     *
     * @return true if authenticated.
     */
    bool isAuthenticated() ;
	
    /**
     * Returns true if currently authenticated anonymously.
     *
     * @return true if authenticated anonymously.
     */
    bool isAnonymous() ;

    /**
     * Closes the connection by setting presence to unavailable then closing the stream to
     * the XMPP server. The XMPPConnection can still be used for connecting to the server
     * again.<p>
     * <p/>
     * This method cleans up all resources used by the connection. Therefore, the roster,
     * listeners and other stateful objects cannot be re-used by simply calling connect()
     * on this connection again. This is unlike the behavior during unexpected disconnects
     * (and subsequent connections). In that case, all state is preserved to allow for
     * more seamless error recovery.
     */
    void disconnect();
	
    /**
     * Closes the connection. A custom unavailable presence is sent to the server, followed
     * by closing the stream. The XMPPConnection can still be used for connecting to the server
     * again. A custom unavilable presence is useful for communicating offline presence
     * information such as "On vacation". Typically, just the status text of the presence
     * packet is set with online information, but most XMPP servers will deliver the full
     * presence packet with whatever data is set.<p>
     * <p/>
     * This method cleans up all resources used by the connection. Therefore, the roster,
     * listeners and other stateful objects cannot be re-used by simply calling connect()
     * on this connection again. This is unlike the behavior during unexpected disconnects
     * (and subsequent connections). In that case, all state is preserved to allow for
     * more seamless error recovery.
     *
     * @param unavailablePresence the presence packet to send during shutdown.
     */
    void disconnect(Presence * unavailablePresence) ;

    /**
     * Closes the connection by setting presence to unavailable then closing the stream to
     * the XMPP server. The shutdown logic will be used during a planned disconnection or when
     * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's
     * packet reader, packet writer, and {@link Roster} will not be removed; thus
     * connection's state is kept.
     *
     * @param unavailablePresence the presence packet to send during shutdown.
     */
    void shutdown(Presence * unavailablePresence) ;
	
    /**
     * Sends the specified packet to the server.
     *
     * @param packet the packet to send.
     */
    void sendPacket(Packet * packet) ;
	
    /**
     * Registers a packet listener with this connection. A packet filter determines
     * which packets will be delivered to the listener. If the same packet listener
     * is added again with a different filter, only the new filter will be used.
     *
     * @param packetListener the packet listener to notify of new packets.
     * @param packetFilter   the packet filter to use.
     */
    void addPacketListener(PacketListener * packetListener, PacketFilter * packetFilter) ;
	
    /**
     * Removes a packet listener from this connection.
     *
     * @param packetListener the packet listener to remove.
     */
    void removePacketListener(PacketListener * packetListener) ;
    
    /**
     * Registers a packet listener with this connection. The listener will be
     * notified of every packet that this connection sends. A packet filter determines
     * which packets will be delivered to the listener. Note that the thread
     * that writes packets will be used to invoke the listeners. Therefore, each
     * packet listener should complete all operations quickly or use a different
     * thread for processing.
     *
     * @param packetListener the packet listener to notify of sent packets.
     * @param packetFilter   the packet filter to use.
     */
    void addPacketWriterListener(PacketListener * packetListener, PacketFilter * packetFilter) ;
    
    /**
     * Removes a packet listener from this connection.
     *
     * @param packetListener the packet listener to remove.
     */
    void removePacketWriterListener(PacketListener * packetListener);
	
    /**
     * Registers a packet interceptor with this connection. The interceptor will be
     * invoked every time a packet is about to be sent by this connection. Interceptors
     * may modify the packet to be sent. A packet filter determines which packets
     * will be delivered to the interceptor.
     *
     * @param packetInterceptor the packet interceptor to notify of packets about to be sent.
     * @param packetFilter      the packet filter to use.
     */
    void addPacketWriterInterceptor(PacketInterceptor * packetInterceptor, PacketFilter * packetFilter) ;
	
    /**
     * Removes a packet interceptor.
     *
     * @param packetInterceptor the packet interceptor to remove.
     */
    void removePacketWriterInterceptor(PacketInterceptor * packetInterceptor);
	
    /**
     * Creates a new packet collector for this connection. A packet filter determines
     * which packets will be accumulated by the collector.
     *
     * @param packetFilter the packet filter to use.
     * @return a new packet collector.
     */
    PacketCollector* createPacketCollector(PacketFilter * packetFilter) ;

	/**
     * Adds a connection listener to this connection that will be notified when
     * the connection closes or fails. The connection needs to already be connected
     * or otherwise an IllegalStateException will be thrown.
     *
     * @param connectionListener a connection listener.
     */
    void addConnectionListener(ConnectionListener * connectionListener) ;

	/**
     * Removes a connection listener from this connection.
     *
     * @param connectionListener a connection listener.
     */
    void removeConnectionListener(ConnectionListener * connectionListener);
	
    /***********************************************
     * TLS code below
     **********************************************/

    /**
     * Returns true if the connection to the server has successfully negotiated TLS. Once TLS
     * has been negotiatied the connection has been secured.
     *
     * @return true if the connection to the server has successfully negotiated TLS.
     */
    bool isUsingTLS();

    /**
     * Returns the SASLAuthentication manager that is responsible for authenticating with
     * the server.
     *
     * @return the SASLAuthentication manager that is responsible for authenticating with
     *         the server.
     */
    SASLAuthentication * getSASLAuthentication() ;

    /**
     * Notification message saying that the server supports TLS so confirm the server that we
     * want to secure the connection.
     *
     * @param required true when the server indicates that TLS is required.
     */
    void startTLSReceived(bool required) ;

    /**
     * The server has indicated that TLS negotiation can start. We now need to secure the
     * existing plain connection and perform a handshake. This method won't return until the
     * connection has finished the handshake or an error occured while securing the connection.
     *
     * @throws Exception if an exception occurs.
     */
    void proceedTLSReceived() ;
	
    /**
     * Sets the available stream compression methods offered by the server.
     *
     * @param methods compression methods offered by the server.
     */
    void setAvailableCompressionMethods(std::vector<std::string> & methods) ;

    /**
     * Returns true if network traffic is being compressed. When using stream compression network
     * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow
     * speed network connection. However, the server will need to use more CPU time in order to
     * un/compress network data so under high load the server performance might be affected.<p>
     * <p/>
     * Note: to use stream compression the smackx.jar file has to be present in the classpath.
     *
     * @return true if network traffic is being compressed.
     */
    bool isUsingCompression() ;

    /**
     * Start using stream compression since the server has acknowledged stream compression.
     *
     * @throws Exception if there is an exception starting stream compression.
     */
    void startStreamCompression() ;	

    /**
     * Notifies the XMPP connection that stream compression was denied so that
     * the connection process can proceed.
     */
    void streamCompressionDenied() ;	

    /**
     * Establishes a connection to the XMPP server and performs an automatic login
     * only if the previous connection state was logged (authenticated). It basically
     * creates and maintains a socket connection to the server.<p>
     * <p/>
     * Listeners will be preserved from a previous connection if the reconnection
     * occurs after an abrupt termination.
     *
     * @throws XMPPException if an error occurs while trying to establish the connection.
     *                       Two possible errors can occur which will be wrapped by an XMPPException --
     *                       UnknownHostException (XMPP error code 504), and IOException (XMPP error code
     *                       502). The error codes and wrapped exceptions can be used to present more
     *                       appropiate error messages to end-users.
     */
    void connect() ;
	
	int getConnectionCounter() { return connectionCounterValue;}


    /**
     * Adds a new listener that will be notified when new XMPPConnections are created. Note
     * that newly created connections will not be actually connected to the server.
     *
     * @param connectionCreationListener a listener interested on new connections.
     */
    static void addConnectionCreationListener( ConnectionCreationListener * connectionCreationListener) ;

    /**
     * Removes a listener that was interested in connection creation events.
     *
     * @param connectionCreationListener a listener interested on new connections.
     */
    static void removeConnectionCreationListener( ConnectionCreationListener * connectionCreationListener) ;	
	
protected: 	
    /**
     * Returns the configuration used to connect to the server.
     *
     * @return the configuration used to connect to the server.
     */
    ConnectionConfiguration * getConfiguration() ;	

private:
	void connectUsingConfiguration(ConnectionConfiguration * config) ;

    /**
     * Initializes the connection by creating a packet reader and writer and opening a
     * XMPP stream to the server.
     *
     * @throws XMPPException if establishing a connection to the server fails.
     */
    void initConnection() ; 
	void initReaderAndWriter();

    /**
     * Returns true if the specified compression method was offered by the server.
     *
     * @param method the method to check.
     * @return true if the specified compression method was offered by the server.
     */
    bool hasAvailableCompressionMethod(std::string method) ;	

    /**
     * Starts using stream compression that will compress network traffic. Traffic can be
     * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network
     * connection. However, the server and the client will need to use more CPU time in order to
     * un/compress network data so under high load the server performance might be affected.<p>
     * <p/>
     * Stream compression has to have been previously offered by the server. Currently only the
     * zlib method is supported by the client. Stream compression negotiation has to be done
     * before authentication took place.<p>
     * <p/>
     * Note: to use stream compression the smackx.jar file has to be present in the classpath.
     *
     * @return true if stream compression negotiation was successful.
     */
    bool useCompression() ;

    /**
     * Request the server that we want to start using stream compression. When using TLS
     * then negotiation of stream compression can only happen after TLS was negotiated. If TLS
     * compression is being used the stream compression should not be used.
     */
    void requestStreamCompression() ;

	void setWasAuthenticated(bool wasAuthenticated);


	static std::vector<ConnectionCreationListener*> connectionEstablishedListeners;

    // Counter to uniquely identify connections that are created. This is distinct from the
    // connection ID, which is a value sent by the server once a connection is made.
    static int connectionCounter;

    /**
     * IP address or host name of the server. This information is only used when
     * creating new socket connections to the server. If this information is not
     * configured then it will be assumed that the host name matches the service name.
     */
	std::string host;
	int port;
	SocketHandler socketHandler;
	Socket * socket;

    /**
     * Hostname of the XMPP server. Usually servers use the same service name as the name
     * of the server. However, there are some servers like google where host would be
     * talk.google.com and the serviceName would be gmail.com.
     */
    std::string serviceName;

    int connectionCounterValue ;
    std::string connectionID ;
	std::string user;
	bool connected;

    /**
     * Flag that indicates if the user is currently authenticated with the server.
     */
	bool authenticated;

    /**
     * Flag that indicates if the user was authenticated with the server when the connection
     * to the server was closed (abruptly or not).
     */
    bool wasAuthenticated;
    bool anonymous;
    bool usingTLS;

    PacketWriter * packetWriter;
    PacketReader * packetReader;

	Roster * roster;
	AccountManager * accountManager;
	SASLAuthentication * saslAuthentication;
	
	std::istream * reader;
	std::ostream * writer;
    /**
     * Collection of available stream compression methods offered by the server.
     */
	std::vector<std::string> compressionMethods;
    /**
     * Flag that indicates if stream compression is actually in use.
     */
    bool usingCompression;
    /**
     * Holds the initial configuration used while creating the connection.
     */
    ConnectionConfiguration * configuration;
    ChatManager * chatManager;
};

#endif //XMPPCONNECTION_H
